package com.kexio.user.service.impl;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.kexio.auth.config.SecurityProperties;
import com.kexio.auth.dto.UserAuthInfo;
import com.kexio.auth.security.PasswordEncoder;
import com.kexio.auth.util.AuthContextUtils;
import com.kexio.common.enums.StatusEnum;
import com.kexio.common.exception.BusinessException;
import com.kexio.common.utils.ValidationUtils;
import com.kexio.user.entity.Menu;
import com.kexio.user.entity.Role;
import com.kexio.user.entity.User;
import com.kexio.user.entity.UserRole;
import com.kexio.user.mapper.DeptMapper;
import com.kexio.user.mapper.RoleMapper;
import com.kexio.user.mapper.UserDataScopeMapper;
import com.kexio.user.mapper.UserMapper;
import com.kexio.user.mapper.UserRoleMapper;
import com.kexio.user.service.MenuService;
import com.kexio.user.service.PermissionSyncService;
import com.kexio.user.service.UserService;
// ❌ 已删除: PermissionSyncService - Sa-Token架构下不再需要权限同步

/**
 * 用户服务实现类 - 集成权限变更通知机制
 * 
 * 实现用户管理的核心业务逻辑，包括：
 * - 用户CRUD操作（集成权限版本控制）
 * - 用户认证信息管理
 * - 用户权限聚合
 * - 用户状态管理
 * - 多租户数据隔离
 * - 权限变更通知（afterCommit）
 * - 权限版本自增（user_perm_version表）
 * 
 * 权限变更通知策略：
 * 1. 用户角色关联变更 → 通知权限变更 + 自增版本
 * 2. 用户状态变更 → 通知权限变更 + 自增版本
 * 3. 用户部门变更 → 通知权限变更 + 自增版本（影响数据权限）
 * 4. 使用事务同步机制确保在事务提交后执行通知
 * 
 * @author Kexio Team
 * @since 1.0.0
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    
    private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
    
    // 必需依赖
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RoleMapper roleMapper;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private SecurityProperties securityProperties;
    
    @Autowired(required = false)
    private PermissionSyncService permissionSyncService;
    
    @Autowired(required = false)
    private com.kexio.user.service.PermissionVersionService permVersionService;

    // 可选依赖
    @Autowired
    private UserRoleMapper userRoleMapper;
    @Autowired
    private UserDataScopeMapper userDataScopeMapper;
    @Autowired
    private DeptMapper deptMapper;
    @Autowired
    private MenuService menuService;
    
    // 现代化缓存服务支持
    
    // SimplifiedCacheKeyManager 预留，暂未使用
    // @Autowired(required = false)
    // private SimplifiedCacheKeyManager simplifiedCacheKeyManager;
    
    // ==================== 基础CRUD操作 ====================
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public User createUser(User user) {
        // 参数验证
        if (user == null) {
            throw BusinessException.invalidParam("user", "用户信息不能为空");
        }
        if (!StringUtils.hasText(user.getUsername())) {
            throw BusinessException.invalidParam("username", "用户名不能为空");
        }
        // 不再在服务层强制要求 tenantId；开启租户时由租户拦截器注入过滤条件
        
        // 检查用户名是否已存在
        if (existsByUsername(user.getUsername(), null)) {
            throw BusinessException.dataExists("用户名", user.getUsername());
        }
        
        // 检查手机号是否已存在
        if (StringUtils.hasText(user.getPhone()) && existsByPhone(user.getPhone(), null)) {
            throw BusinessException.dataExists("手机号", user.getPhone());
        }
        
        // 检查邮箱是否已存在
        if (StringUtils.hasText(user.getEmail()) && existsByEmail(user.getEmail(), null)) {
            throw BusinessException.dataExists("邮箱", user.getEmail());
        }
        
        // 设置默认值
        if (user.getStatus() == null) {
            user.setStatus(StatusEnum.ENABLED.getValue()); // 默认启用
        }
        user.setPasswordErrorCount(0);
        user.setPermVersion(0L);
        
        // 设置邮箱验证状态（默认未验证）
        if (user.getEmailVerified() == null) {
            user.setEmailVerified(false);
        }
        
        // 密码加密
        if (StringUtils.hasText(user.getPassword())) {
            user.setPassword(passwordEncoder.encode(user.getPassword()));
            user.setLastPasswordChangeAt(LocalDateTime.now());
        }
        
        // 保存用户
        userMapper.insert(user);
        
        return user;
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public User updateUser(String userId, User user) {
        ValidationUtils.notNull(userId, "用户ID不能为空");
        ValidationUtils.notNull(user, "用户信息不能为空");
        
        // 查询现有用户
        User existingUser = getUserById(userId);
        ValidationUtils.notNull(existingUser, "用户不存在，ID：" + userId);
        
        // 检查用户名唯一性
        if (StringUtils.hasText(user.getUsername()) && !user.getUsername().equals(existingUser.getUsername())) {
            if (existsByUsername(user.getUsername(), userId)) {
                throw BusinessException.dataExists("用户名已存在：" + user.getUsername());
            }
            existingUser.setUsername(user.getUsername());
        }
        
        // 检查手机号唯一性
        if (StringUtils.hasText(user.getPhone()) && !user.getPhone().equals(existingUser.getPhone())) {
            if (existsByPhone(user.getPhone(), userId)) {
                throw BusinessException.dataExists("手机号已存在：" + user.getPhone());
            }
            existingUser.setPhone(user.getPhone());
        }
        
        // 检查邮箱唯一性
        if (StringUtils.hasText(user.getEmail()) && !user.getEmail().equals(existingUser.getEmail())) {
            if (existsByEmail(user.getEmail(), userId)) {
                throw new IllegalArgumentException("邮箱已存在：" + user.getEmail());
            }
            existingUser.setEmail(user.getEmail());
        }
        
        // 更新其他字段
        if (StringUtils.hasText(user.getNickname())) {
            existingUser.setNickname(user.getNickname());
        }
        if (StringUtils.hasText(user.getAvatarUrl())) {
            existingUser.setAvatarUrl(user.getAvatarUrl());
        }
        if (user.getStatus() != null) {
            existingUser.setStatus(user.getStatus());
        }
        // 检测是否有影响权限的变更
        boolean needPermissionSync = false;
        String changeReason = "";
        
        if (user.getDeptId() != null && !Objects.equals(existingUser.getDeptId(), user.getDeptId())) {
            existingUser.setDeptId(user.getDeptId());
            needPermissionSync = true;
            changeReason = "USER_DEPT_CHANGED";
        }
        
        // 状态变更也需要权限同步
        if (user.getStatus() != null && !Objects.equals(existingUser.getStatus(), user.getStatus())) {
            needPermissionSync = true;
            changeReason = user.getStatus() == 1 ? "USER_ENABLED" : "USER_DISABLED";
        }

        // 保存更新
        userMapper.updateById(existingUser);
        
        // 🎯 Sa-Token架构：权限相关变更需要同步缓存
        if (needPermissionSync) {
            syncUserPermissionAfterRoleChange(userId, changeReason);
        }
        
        return existingUser;
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteUser(String userId) {
        if (userId == null) {
            throw new IllegalArgumentException("用户ID不能为空");
        }
        
        User user = getUserById(userId);
        if (user == null) {
            return false;
        }
        
        // 逻辑删除 - 直接使用MyBatis Plus的逻辑删除
        boolean result = userMapper.deleteById(userId) > 0;
        
        if (result) {
            // 🎯 Sa-Token架构：用户删除后清理权限缓存
            syncUserPermissionAfterRoleChange(userId, "USER_DELETED");
        }
        
        return result;
    }
    
    @Override
    public User getUserById(String userId) {
        if (userId == null) {
            return null;
        }
        // 统一交由租户拦截器处理，不在此处前置要求 tenantId
        
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getId, userId);
        
        return userMapper.selectOne(wrapper);
    }
    
    @Override
    public User getUserByUsername(String username) {
        if (!StringUtils.hasText(username)) {
            return null;
        }
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getUsername, username);
        return userMapper.selectOne(wrapper);
    }
    
    @Override
    public User getUserByPhone(String phone) {
        if (!StringUtils.hasText(phone)) {
            return null;
        }
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getPhone, phone);
        return userMapper.selectOne(wrapper);
    }
    
    @Override
    public User getUserByEmail(String email) {
        if (!StringUtils.hasText(email)) {
            return null;
        }
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getEmail, email);
        return userMapper.selectOne(wrapper);
    }
    
    // ==================== 用户列表查询 ====================
    
    @Override
    public List<User> getAllUsers() {
        // 获取所有用户（不分页）
        // 注意：此方法会查询所有用户，仅用于特定场景（如在线用户检测）
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getStatus, StatusEnum.ENABLED.getValue()); // 只查询启用的用户
        wrapper.orderByDesc(User::getCreateTime);
        
        List<User> users = userMapper.selectList(wrapper);
        logger.debug("查询到 {} 个启用用户", users != null ? users.size() : 0);
        return users;
    }
    
    @Override
    public Page<User> getUserPage(Long current, Long size) {
        // 统一交由租户拦截器处理，不在此处前置要求 tenantId
        
        Page<User> page = new Page<>(
            com.kexio.common.page.PageUtils.normalizePageNo(current),
            com.kexio.common.page.PageUtils.normalizePageSize(size)
        );
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.orderByDesc(User::getCreateTime);
        
        return userMapper.selectPage(page, wrapper);
    }
    
    @Override
    public List<User> getUserList(String username, String phone, String email, String status, String deptId) {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        
        if (StringUtils.hasText(username)) {
            wrapper.like(User::getUsername, username);
        }
        if (StringUtils.hasText(phone)) {
            wrapper.like(User::getPhone, phone);
        }
        if (StringUtils.hasText(email)) {
            wrapper.like(User::getEmail, email);
        }
        if (StringUtils.hasText(status) && status.chars().allMatch(Character::isDigit)) {
            wrapper.eq(User::getStatus, Integer.valueOf(status));
        }
        if (deptId != null) {
            wrapper.eq(User::getDeptId, deptId);
        }
        
        wrapper.orderByDesc(User::getCreateTime);
        
        return userMapper.selectList(wrapper);
    }
    
    @Override
    public List<User> getUsersByDept(String deptId) {
        if (!StringUtils.hasText(deptId)) {
            throw new IllegalArgumentException("部门ID不能为空");
        }
        
        return userMapper.selectUsersByDept(deptId);
    }
    
    @Override
    public List<User> getUsersByDepts(List<String> deptIds) {
        if (CollectionUtils.isEmpty(deptIds)) {
            throw new IllegalArgumentException("部门ID列表不能为空");
        }
        
        List<String> ids = deptIds.stream().filter(StringUtils::hasText).collect(Collectors.toList());
        return userMapper.selectUsersByDepts(ids);
    }
    
    @Override
    public List<User> getUsersInDataScope(String currentUserId, String dataScopeType, List<String> deptIds) {
        if (!StringUtils.hasText(currentUserId) || !StringUtils.hasText(dataScopeType)) {
            throw new IllegalArgumentException("当前用户ID、数据权限类型不能为空");
        }
        
        List<String> ids = deptIds != null ? deptIds.stream().filter(StringUtils::hasText).collect(Collectors.toList()) : null;
        return userMapper.selectUsersInDataScope(currentUserId, dataScopeType, ids);
    }
    
    // ==================== 用户认证相关 ====================
    
    @Override
    public User authenticate(String loginAccount, String password, String tenantId) {
        // 基础参数验证
        if (!StringUtils.hasText(loginAccount) || !StringUtils.hasText(password)) {
            return null;
        }
        
        // 租户ID处理：根据配置决定是否强制要求租户ID
        String effectiveTenantId = tenantId;
        if (securityProperties.getTenant().isEnabled()) {
            // 启用租户模式时，必须提供租户ID
            if (!StringUtils.hasText(tenantId)) {
                logger.warn("租户模式已启用，但未提供租户ID，认证失败: loginAccount={}", loginAccount);
                return null;
            }
        } else {
            // 租户模式关闭时，使用默认租户ID
            if (!StringUtils.hasText(tenantId)) {
                effectiveTenantId = "default";
                logger.debug("租户模式已关闭，使用默认租户ID: {}", effectiveTenantId);
            }
        }
        
        // 根据登录账号类型查询用户
        // 临时设置租户上下文，确保查询用户时使用正确的租户ID
        String originalTenant = com.kexio.auth.context.TenantContextHolder.getTenant();
        User user;
        try {
            com.kexio.auth.context.TenantContextHolder.setTenant(effectiveTenantId);
            
            if (loginAccount.contains("@")) {
                user = getUserByEmail(loginAccount);
            } else if (loginAccount.matches("\\d{11}")) {
                user = getUserByPhone(loginAccount);
            } else {
                user = getUserByUsername(loginAccount);
            }
        } finally {
            // 恢复原始租户上下文
            if (originalTenant != null) {
                com.kexio.auth.context.TenantContextHolder.setTenant(originalTenant);
            } else {
                com.kexio.auth.context.TenantContextHolder.clear();
            }
        }
        
        Integer st = (user != null ? user.getStatus() : null);
        if (user == null || st == null || st != 1) {
            return null;
        }
        
        // 检查用户是否被锁定
        if (isUserLocked(user)) {
            return null;
        }
        
        // 验证密码
        if (passwordEncoder.matches(password, user.getPassword())) {
            // 密码正确，清零错误次数
            Integer pec = user.getPasswordErrorCount();
            if (pec != null && pec > 0) {
                clearPasswordErrorCount(user.getId());
            }
            return user;
        } else {
            // 密码错误，增加错误次数
            int errorCount = incrementPasswordErrorCount(user.getId());
            
            // 检查是否需要锁定账户（读取配置：失败次数与锁定分钟）
            int maxAttempts = securityProperties.getSecurity().getLogin().getFailureMaxAttempts();
            int lockMinutes = securityProperties.getSecurity().getLogin().getFailureLockMinutes();
            if (errorCount >= maxAttempts) {
                lockUser(user.getId(), lockMinutes);
                logger.warn("用户密码错误达到上限，账户被锁定: userId={}, tenantId={}, errorCount={}", 
                           user.getId(), effectiveTenantId, errorCount);
            }
            
            return null;
        }
    }
    
    // ==================== 其他方法的基础实现 ====================
    
    // 其他方法的实现已完成或正在完善中..."
    // 为了项目完整性，这里提供基础的方法存根
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updatePassword(String userId, String oldPassword, String newPassword) {
        if (userId == null || !StringUtils.hasText(oldPassword) || 
            !StringUtils.hasText(newPassword)) {
            throw new IllegalArgumentException("用户ID、旧密码、新密码不能为空");
        }
        
        // 查询用户信息
        User user = getUserById(userId);
        if (user == null) {
            throw new IllegalArgumentException("用户不存在，ID：" + userId);
        }
        
        // 验证旧密码
        if (!passwordEncoder.matches(oldPassword, user.getPassword())) {
            throw new IllegalArgumentException("旧密码错误");
        }
        
        // 检查新密码是否与旧密码相同
        if (passwordEncoder.matches(newPassword, user.getPassword())) {
            throw new IllegalArgumentException("新密码不能与旧密码相同");
        }
        
        // 加密新密码
        String encodedNewPassword = passwordEncoder.encode(newPassword);
        
        // 更新密码
        user.setPassword(encodedNewPassword);
        user.setLastPasswordChangeAt(LocalDateTime.now());
        user.setPasswordErrorCount(0); // 重置错误次数
        
        int result = userMapper.updateById(user);
        
        if (result > 0) {
            // 通知权限变更（密码修改可能影响会话）
            String currentTenantId = com.kexio.auth.context.TenantContextHolder.getTenant();
            internalNotifyUserPermissionChanged(currentTenantId, userId, "PASSWORD_CHANGED");
            logger.info("用户密码更新成功: userId={}", userId);
        }
        
        return result > 0;
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean resetPassword(String userId, String newPassword, Long operatorId) {
        if (userId == null || !StringUtils.hasText(newPassword)) {
            throw new IllegalArgumentException("用户ID、新密码不能为空");
        }
        
        // 查询用户信息
        User user = getUserById(userId);
        if (user == null) {
            throw new IllegalArgumentException("用户不存在，ID：" + userId);
        }
        
        // 加密新密码
        String encodedNewPassword = passwordEncoder.encode(newPassword);
        
        // 更新密码相关字段
        user.setPassword(encodedNewPassword);
        user.setLastPasswordChangeAt(LocalDateTime.now());
        user.setPasswordErrorCount(0); // 重置错误次数
        if (operatorId != null) {
            user.setUpdateBy(operatorId.toString());
        }
        
        // 解锁用户账户（如果被锁定）
        user.setLockTime(null);
        user.setUnlockTime(null);
        
        int result = userMapper.updateById(user);
        
        if (result > 0) {
            // 通知权限变更（密码重置需要重新登录）
            String currentTenantId = com.kexio.auth.context.TenantContextHolder.getTenant();
            internalNotifyUserPermissionChanged(currentTenantId, userId, "PASSWORD_RESET");
            logger.info("用户密码重置成功: userId={}, operatorId={}", userId, operatorId);
        }
        
        return result > 0;
    }
    
    @Override
    public boolean updateLastLoginTime(String userId, LocalDateTime lastLoginTime) {
        return userMapper.updateLastLoginTimeById(userId, lastLoginTime) > 0;
    }
    
    @Override
    public int incrementPasswordErrorCount(String userId) {
        User user = getUserById(userId);
        if (user == null) {
        return 0;
        }
        Integer current = user.getPasswordErrorCount();
        int newCount = (current != null ? current : 0) + 1;
        userMapper.incrementPasswordErrorCountById(userId);
        return newCount;
    }
    
    @Override
    public boolean clearPasswordErrorCount(String userId) {
        return userMapper.clearPasswordErrorCountById(userId) > 0;
    }
    
    @Override
    public boolean lockUser(String userId, int lockMinutes) {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime unlockTime = now.plusMinutes(lockMinutes);
        return userMapper.lockUserById(userId, now, unlockTime) > 0;
    }
    
    @Override
    public boolean unlockUser(String userId) {
        return userMapper.unlockUserById(userId) > 0;
    }
    
    @Override
    public boolean isUserLocked(User user) {
        return user.getUnlockTime() != null && LocalDateTime.now().isBefore(user.getUnlockTime());
    }
    
    @Override
    public List<String> getUserPermissions(String userId) {
        logger.debug("🚀 Sa-Token架构下获取用户权限: userId={}", userId);
        
        // Sa-Token架构下权限获取已简化，使用直接查询
        return userMapper.selectUserPermissions(userId);
    }
    
    @Override
    public List<String> getUserRoles(String userId) {
        logger.debug("🚀 Sa-Token架构下获取用户角色: userId={}", userId);
        
        // Sa-Token架构下角色获取已简化，使用直接查询
        return userMapper.selectUserRoles(userId);
    }
    
    @Override
    public List<Menu> getUserMenus(String userId) {
        logger.debug("🚀 Sa-Token架构下获取用户菜单: userId={}", userId);
        
        // Sa-Token架构下菜单获取已简化，使用直接查询
        return userMapper.selectUserMenus(userId);
    }
    
    @Override
    public Object getUserDataScope(String userId) {
        // Sa-Token架构下简化数据权限处理，返回null
        // 具体的数据权限逻辑由拦截器和注解处理
        logger.debug("Sa-Token架构下数据权限处理简化: userId={}", userId);
        return null;
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean assignRoles(String userId, List<String> roleIds, Long operatorId) {
        if (CollectionUtils.isEmpty(roleIds)) {
            return true;
        }
        
        // 真正实现batchAssignRoles方法
        // 1. 先删除用户现有的角色关联
        userRoleMapper.delete(
            new LambdaQueryWrapper<UserRole>()
                .eq(UserRole::getUserId, userId)
        );
        
        // 2. ✅ 批量插入新的角色关联（一条SQL搞定）
        if (!roleIds.isEmpty()) {
            String createdBy = AuthContextUtils.getCurrentUserId();
            userRoleMapper.batchAssignRoles(userId, roleIds, createdBy);
            logger.info("批量分配角色成功: userId={}, roleCount={}", userId, roleIds.size());
        }
        
        // 4. 🎯 Sa-Token架构：同步权限变更到缓存
        syncUserPermissionAfterRoleChange(userId, "USER_ROLES_ASSIGNED");
        
        // 🚀 实时同步用户权限变更，无需重新登录
        if (permissionSyncService != null) {
            try {
                String tenantId = AuthContextUtils.getCurrentTenantId();
                permissionSyncService.syncUserPermissions(userId, tenantId, "角色分配变更");
                logger.info("🔄 用户权限实时同步完成: userId={}", userId);
            } catch (Exception e) {
                logger.warn("用户权限同步失败，但角色分配成功: userId={}, error={}", userId, e.getMessage());
            }
        }
        
        // 更新用户权限版本号
        if (permVersionService != null) {
            try {
                permVersionService.onUserRoleChanged(userId);
                logger.info("✅ 用户角色分配，权限版本已更新: userId={}", userId);
            } catch (Exception e) {
                logger.warn("⚠️ 更新用户权限版本失败: userId={}, error={}", userId, e.getMessage());
            }
        }
        
        return true;
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeRoles(String userId, List<String> roleIds) {
        if (CollectionUtils.isEmpty(roleIds)) {
            return true;
        }
        
        // 真正实现batchDeleteByUserAndRoles方法
        boolean result = batchDeleteUserRoles(userId, roleIds);
        
        if (result) {
            // 🎯 Sa-Token架构：同步权限变更到缓存
            syncUserPermissionAfterRoleChange(userId, "USER_ROLES_REMOVED");
            
            // ✅ 新增：更新用户权限版本号
            if (permVersionService != null) {
                try {
                    permVersionService.onUserRoleChanged(userId);
                    logger.info("✅ 用户角色移除，权限版本已更新: userId={}", userId);
                } catch (Exception e) {
                    logger.warn("⚠️ 更新用户权限版本失败: userId={}, error={}", userId, e.getMessage());
                }
            }
        }
        
        return result;
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean syncRoles(String userId, List<String> roleIds, Long operatorId) {
        // 真正实现syncUserRoles方法
        try {
            return syncUserRolesFromDatabase(userId, roleIds, operatorId);
        } catch (Exception e) {
            logger.error("同步用户角色失败: userId={}, roleIds={}, error={}", userId, roleIds, e.getMessage(), e);
            return false;
        }
    }

    @Override
    @org.springframework.transaction.annotation.Transactional(rollbackFor = Exception.class)
    public void rebuildUsersProjection(java.util.List<String> userIds, String reason) {
        if (userIds == null || userIds.isEmpty()) {
            return;
        }
        // Sa-Token架构下简化投影重建
        logger.debug("Sa-Token架构下跳过投影重建: userIds={}", userIds);
        // Sa-Token架构下权限信息存储在Session中，无需重建投影
        String currentTenantId = AuthContextUtils.getCurrentTenantId();
        logger.info("Sa-Token架构下跳过权限投影重建: tenantId={}, userCount={}, reason={}", currentTenantId, userIds.size(), reason);
    }
    
    @Override
    public boolean updatePermVersion(String userId, Long permVersion) {
        return userMapper.updatePermVersionById(userId, permVersion) > 0;
    }
    
    @Override
    public Long getUserPermVersion(String userId) {
        User user = getUserById(userId);
        return user != null ? user.getPermVersion() : null;
    }
    
    @Override
    public Long countUsers() {
        // 租户拦截器会自动处理租户过滤
        return userMapper.selectCount(new LambdaQueryWrapper<>());
    }
    
    @Override
    public Long countActiveUsers() {
        // 查询最近30天有登录记录的用户数量
        LocalDateTime thirtyDaysAgo = LocalDateTime.now().minusDays(30);
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.ge(User::getLastLoginTime, thirtyDaysAgo);
        return userMapper.selectCount(wrapper);
    }
    
    @Override
    public Long countTotalUsers() {
        try {
            return userMapper.selectCount(null);
        } catch (Exception e) {
            logger.error("统计总用户数量失败", e);
            return 0L;
        }
    }
    
    @Override
    public Long countTotalTenants() {
        try {
            // 查询不同的租户ID数量
            LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
            wrapper.select(User::getTenantId).isNotNull(User::getTenantId);
            List<User> users = userMapper.selectList(wrapper);
            return users.stream()
                .map(User::getTenantId)
                .distinct()
                .count();
        } catch (Exception e) {
            logger.error("统计总租户数量失败", e);
            return 1L; // 默认至少有一个租户
        }
    }
    
    @Override
    public Long countActiveTenants() {
        try {
            // 查询30天内活跃的租户数量
            LocalDateTime thirtyDaysAgo = LocalDateTime.now().minusDays(30);
            LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
            wrapper.select(User::getTenantId)
                   .isNotNull(User::getTenantId)
                   .ge(User::getLastLoginTime, thirtyDaysAgo);
            List<User> users = userMapper.selectList(wrapper);
            return users.stream()
                .map(User::getTenantId)
                .distinct()
                .count();
        } catch (Exception e) {
            logger.error("统计活跃租户数量失败", e);
            return 1L; // 默认至少有一个活跃租户
        }
    }
    
    @Override
    public java.util.Map<String, Long> getTenantUserCounts() {
        try {
            LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
            wrapper.isNotNull(User::getTenantId);
            List<User> users = userMapper.selectList(wrapper);
            return users.stream()
                    .collect(Collectors.groupingBy(
                        user -> user.getTenantId() != null ? user.getTenantId() : "default",
                        Collectors.counting()
                    ));
        } catch (Exception e) {
            logger.error("获取租户用户数统计失败", e);
            return java.util.Map.of("default", 0L);
        }
    }
    
    @Override
    public boolean existsByUsername(String username, String excludeUserId) {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getUsername, username);
        
        if (excludeUserId != null) {
            wrapper.ne(User::getId, excludeUserId);
        }
        
        return userMapper.selectCount(wrapper) > 0;
    }
    
    @Override
    public boolean existsByPhone(String phone, String excludeUserId) {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getPhone, phone);
        
        if (excludeUserId != null) {
            wrapper.ne(User::getId, excludeUserId);
        }
        
        return userMapper.selectCount(wrapper) > 0;
    }
    
    @Override
    public boolean existsByEmail(String email, String excludeUserId) {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getEmail, email);
        
        if (excludeUserId != null) {
            wrapper.ne(User::getId, excludeUserId);
        }
        
        return userMapper.selectCount(wrapper) > 0;
    }
    
    // ==================== Controller层扩展方法实现 ====================
    
    @Override
    public User createUser(com.kexio.user.dto.UserDTO request) {
        if (request == null) {
            throw new IllegalArgumentException("用户创建请求不能为空");
        }
        
        // DTO转换为Entity
        User user = new User();
        user.setTenantId(request.getTenantId());
        user.setUsername(request.getUsername());
        user.setPassword(request.getPassword());
        user.setNickname(request.getNickname());
        user.setPhone(request.getPhone());
        user.setEmail(request.getEmail());
        user.setAvatarUrl(request.getAvatarUrl());
        user.setDeptId(request.getDeptId());
        user.setStatus(Objects.requireNonNullElse(request.getStatus(), 1));
        user.setDescription(request.getDescription());
        user.setCreateBy(request.getCreateBy());
        
        return createUser(user);
    }
    
    @Override
    public User updateUser(String userId, com.kexio.user.dto.UserDTO request) {
        if (userId == null || request == null) {
            throw new IllegalArgumentException("用户ID和更新请求不能为空");
        }
        
        // DTO转换为Entity
        User user = new User();
        user.setTenantId(request.getTenantId());
        user.setUsername(request.getUsername());
        user.setNickname(request.getNickname());
        user.setPhone(request.getPhone());
        user.setEmail(request.getEmail());
        user.setAvatarUrl(request.getAvatarUrl());
        user.setDeptId(request.getDeptId());
        user.setStatus(request.getStatus());
        user.setDescription(request.getDescription());
        user.setUpdateBy(request.getUpdateBy());
        
        return updateUser(userId, user);
    }
    
    @Override
    public com.kexio.common.dto.PageResponse<User> queryUsers(com.kexio.user.dto.UserDTO queryRequest) {
        if (queryRequest == null) {
            throw new IllegalArgumentException("查询请求不能为空");
        }
        
        // 租户ID由MyBatis租户拦截器自动处理，无需显式设置
        Long current = com.kexio.common.page.PageUtils.normalizePageNo(queryRequest.getCurrent());
        Long size = com.kexio.common.page.PageUtils.normalizePageSize(queryRequest.getSize());
        
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        
        // 添加查询条件
        if (StringUtils.hasText(queryRequest.getUsername())) {
            wrapper.like(User::getUsername, queryRequest.getUsername());
        }
        if (StringUtils.hasText(queryRequest.getNickname())) {
            wrapper.like(User::getNickname, queryRequest.getNickname());
        }
        if (StringUtils.hasText(queryRequest.getPhone())) {
            wrapper.like(User::getPhone, queryRequest.getPhone());
        }
        if (StringUtils.hasText(queryRequest.getEmail())) {
            wrapper.like(User::getEmail, queryRequest.getEmail());
        }
        if (queryRequest.getStatus() != null) {
            wrapper.eq(User::getStatus, queryRequest.getStatus());
        }
        if (queryRequest.getDeptId() != null) {
            wrapper.eq(User::getDeptId, queryRequest.getDeptId());
        }
        
        wrapper.orderByDesc(User::getCreateTime);
        
        Page<User> page = new Page<>(
            com.kexio.common.page.PageUtils.normalizePageNo(current),
            com.kexio.common.page.PageUtils.normalizePageSize(size)
        );
        Page<User> result = userMapper.selectPage(page, wrapper);
        
        // 转换为PageResponse
        return com.kexio.common.dto.PageResponse.of(
            result.getRecords(), result.getTotal(), result.getCurrent(), result.getSize()
        );
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateUserStatus(String userId, Integer status) {
        if (userId == null || status == null) {
            throw new IllegalArgumentException("用户ID和状态不能为空");
        }
        
        // 验证状态值
        if (status != 0 && status != 1) {
            throw new IllegalArgumentException("用户状态值非法，只能为0（禁用）或1（启用）");
        }
        
        // 查询用户信息
        User user = userMapper.selectById(userId);
        if (user == null) {
            throw new IllegalArgumentException("用户不存在，ID：" + userId);
        }
        
        // 检查状态是否发生变化
        if (Objects.equals(user.getStatus(), status)) {
            return true; // 状态未变化
        }
        
        // 更新状态
        user.setStatus(status);
        
        int result = userMapper.updateById(user);
        
        if (result > 0) {
            // 通知权限变更（用户状态变化影响认证）
            String reason = status == 1 ? "USER_ENABLED" : "USER_DISABLED";
            internalNotifyUserPermissionChanged(user.getTenantId(), userId, reason);
            logger.info("用户状态更新成功: userId={}, newStatus={}", userId, status);
        }
        
        return result > 0;
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String resetUserPassword(String userId) {
        if (userId == null) {
            throw new IllegalArgumentException("用户ID不能为空");
        }
        
        // 查询用户信息
        User user = userMapper.selectById(userId);
        if (user == null) {
            throw new IllegalArgumentException("用户不存在，ID：" + userId);
        }
        
        // 生成随机密码并返回，以便管理员可以告知用户
        String newPassword = generateRandomPassword();
        boolean success = resetPassword(userId, newPassword, null);
        
        if (!success) {
            throw BusinessException.operationFailed("密码重置失败");
        }
        
        logger.info("用户密码重置成功: userId={}", userId);
        
        // 返回新密码，管理员需要通过安全方式（如邮件、短信、口头）告知用户
        // 注意：接口层面应该有权限控制，只有管理员能调用此方法
        return newPassword;
        
        // TODO: 后续优化方案
        // 1. 集成邮件/短信服务，自动发送新密码给用户
        // 2. 生成临时密码重置链接，用户点击后自行设置新密码
        // 3. 强制用户首次登录时修改密码
    }
    
    @Override
    public void exportUsers(com.kexio.user.dto.UserDTO queryRequest, 
                           jakarta.servlet.http.HttpServletResponse response) {
        if (queryRequest == null || response == null) {
            throw new IllegalArgumentException("查询请求和响应对象不能为空");
        }
        
        try {
            // 查询用户数据
            List<User> users = getUserList(queryRequest.getUsername(),
                                          queryRequest.getPhone(),
                                          queryRequest.getEmail(),
                                          queryRequest.getStatus() != null ? String.valueOf(queryRequest.getStatus()) : null,
                                          queryRequest.getDeptId());
            
            // 转换为User DTO
            List<com.kexio.user.dto.UserDTO> dtoList = users.stream()
                .map(this::convertToDTO)
                .collect(java.util.stream.Collectors.toList());
            
            // 使用Excel工具类导出
            String fileName = "用户列表_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
            com.kexio.common.excel.ExcelUtils.exportExcel(dtoList, com.kexio.user.dto.UserDTO.class, fileName, response);
            
            logger.info("用户数据导出成功: tenantId={}, count={}", queryRequest.getTenantId(), users.size());
            
        } catch (java.io.IOException | RuntimeException e) {
            logger.error("用户数据导出失败: tenantId={}, error={}", queryRequest.getTenantId(), e.getMessage(), e);
            throw BusinessException.operationFailed("用户数据导出失败: " + e.getMessage());
        }
    }
    
    @Override
    public java.util.Map<String, Object> importUsers(org.springframework.web.multipart.MultipartFile file, Boolean updateSupport) {
        if (file == null || file.isEmpty()) {
            throw new IllegalArgumentException("上传文件不能为空");
        }
        
        Map<String, Object> result = new HashMap<>();
        int successCount = 0;
        int failureCount = 0;
        List<String> errorMessages = new ArrayList<>();
        
        try {
            // 使用Excel工具类导入
            List<com.kexio.user.dto.UserDTO> dtoList = 
                com.kexio.common.excel.ExcelUtils.importExcel(file.getInputStream(), com.kexio.user.dto.UserDTO.class);
            
            String tenantId = AuthContextUtils.getCurrentTenantId();
            
            for (int i = 0; i < dtoList.size(); i++) {
                com.kexio.user.dto.UserDTO dto = dtoList.get(i);
                try {
                    // 验证必填字段
                    if (dto.getUsername() == null || dto.getUsername().trim().isEmpty()) {
                        throw new IllegalArgumentException("用户名不能为空");
                    }
                    if (dto.getNickname() == null || dto.getNickname().trim().isEmpty()) {
                        throw new IllegalArgumentException("真实姓名不能为空");
                    }
                    
                    // 检查用户是否已存在
                    User existingUser = userMapper.selectOne(
                        new LambdaQueryWrapper<User>()
                            .eq(User::getUsername, dto.getUsername())
                            .eq(User::getTenantId, tenantId)
                    );
                    
                    if (existingUser != null) {
                        if (Boolean.TRUE.equals(updateSupport)) {
                            // 更新现有用户
                            updateUserFromDTO(existingUser, dto);
                            userMapper.updateById(existingUser);
                            logger.info("更新用户: username={}", dto.getUsername());
                        } else {
                            throw new IllegalArgumentException("用户名已存在，不支持更新");
                        }
                    } else {
                        // 创建新用户
                        User newUser = createUserFromDTO(dto, tenantId);
                        userMapper.insert(newUser);
                        logger.info("创建用户: username={}", dto.getUsername());
                    }
                    
                    successCount++;
                } catch (Exception e) {
                    failureCount++;
                    errorMessages.add(String.format("第%d行: %s", i + 2, e.getMessage())); // +2因为表头占一行，且从0开始
                    logger.error("导入第{}行失败: {}", i + 2, e.getMessage());
                }
            }
            
            result.put("total", dtoList.size());
            result.put("successCount", successCount);
            result.put("failureCount", failureCount);
            result.put("errorMessages", errorMessages);
            
            logger.info("用户数据导入完成: total={}, success={}, failure={}", dtoList.size(), successCount, failureCount);
            
        } catch (java.io.IOException e) {
            logger.error("读取Excel文件失败", e);
            throw BusinessException.operationFailed("读取Excel文件失败: " + e.getMessage());
        }
        
        return result;
    }
    
    @Override
    public void downloadImportTemplate(jakarta.servlet.http.HttpServletResponse response) {
        try {
            // 创建空的模板数据（包含示例行）
            List<com.kexio.user.dto.UserDTO> templateData = new ArrayList<>();
            com.kexio.user.dto.UserDTO example = new com.kexio.user.dto.UserDTO();
            example.setUsername("zhangsan");
            example.setNickname("张三");
            example.setPhone("13800138000");
            example.setEmail("zhangsan@example.com");
            example.setDeptName("技术部");
            example.setStatus(1);
            example.setDescription("示例用户");
            templateData.add(example);
            
            // 使用Excel工具类导出模板
            String fileName = "用户导入模板";
            com.kexio.common.excel.ExcelUtils.exportExcel(templateData, com.kexio.user.dto.UserDTO.class, fileName, response);
            
            logger.info("下载用户导入模板成功");
            
        } catch (java.io.IOException e) {
            logger.error("下载用户导入模板失败", e);
            throw BusinessException.operationFailed("下载模板失败: " + e.getMessage());
        }
    }
    
    /**
     * 转换User为UserDTO（用于Excel导出）
     */
    private com.kexio.user.dto.UserDTO convertToDTO(User user) {
        com.kexio.user.dto.UserDTO dto = new com.kexio.user.dto.UserDTO();
        dto.setUsername(user.getUsername());
        dto.setNickname(user.getNickname());
        dto.setPhone(user.getPhone());
        dto.setEmail(user.getEmail());
        dto.setStatus(user.getStatus());
        dto.setCreateTime(user.getCreateTime());
        dto.setDescription(user.getDescription());
        
        // 查询部门名称
        if (user.getDeptId() != null) {
            try {
                com.kexio.user.entity.Dept dept = deptMapper.selectById(user.getDeptId());
                if (dept != null) {
                    dto.setDeptName(dept.getDeptName());
                }
            } catch (Exception e) {
                logger.warn("查询部门失败: deptId={}", user.getDeptId());
            }
        }
        
        return dto;
    }
    
    /**
     * 从UserDTO创建User（用于Excel导入）
     */
    private User createUserFromDTO(com.kexio.user.dto.UserDTO dto, String tenantId) {
        User user = new User();
        user.setId(UUID.randomUUID().toString());
        user.setTenantId(tenantId);
        user.setUsername(dto.getUsername());
        user.setNickname(dto.getNickname());
        user.setPhone(dto.getPhone());
        user.setEmail(dto.getEmail());
        user.setStatus(dto.getStatus() != null ? dto.getStatus() : 1);
        user.setDescription(dto.getDescription());
        
        // 设置默认密码
        user.setPassword(passwordEncoder.encode("123456"));
        
        // 查找部门ID
        if (dto.getDeptName() != null && !dto.getDeptName().isEmpty()) {
            com.kexio.user.entity.Dept dept = deptMapper.selectOne(
                new LambdaQueryWrapper<com.kexio.user.entity.Dept>()
                    .eq(com.kexio.user.entity.Dept::getDeptName, dto.getDeptName())
                    .eq(com.kexio.user.entity.Dept::getTenantId, tenantId)
            );
            if (dept != null) {
                user.setDeptId(dept.getId());
            }
        }
        
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(LocalDateTime.now());
        user.setCreateBy(AuthContextUtils.getCurrentUserId());
        user.setUpdateBy(AuthContextUtils.getCurrentUserId());
        
        return user;
    }
    
    /**
     * 从UserDTO更新User（用于Excel导入）
     */
    private void updateUserFromDTO(User user, com.kexio.user.dto.UserDTO dto) {
        if (dto.getNickname() != null) {
            user.setNickname(dto.getNickname());
        }
        if (dto.getPhone() != null) {
            user.setPhone(dto.getPhone());
        }
        if (dto.getEmail() != null) {
            user.setEmail(dto.getEmail());
        }
        if (dto.getStatus() != null) {
            user.setStatus(dto.getStatus());
        }
        if (dto.getDescription() != null) {
            user.setDescription(dto.getDescription());
        }
        
        // 更新部门
        if (dto.getDeptName() != null && !dto.getDeptName().isEmpty()) {
            com.kexio.user.entity.Dept dept = deptMapper.selectOne(
                new LambdaQueryWrapper<com.kexio.user.entity.Dept>()
                    .eq(com.kexio.user.entity.Dept::getDeptName, dto.getDeptName())
                    .eq(com.kexio.user.entity.Dept::getTenantId, user.getTenantId())
            );
            if (dept != null) {
                user.setDeptId(dept.getId());
            }
        }
        
        user.setUpdateTime(LocalDateTime.now());
        user.setUpdateBy(AuthContextUtils.getCurrentUserId());
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int batchDeleteUsers(List<String> userIds) {
        if (userIds == null || userIds.isEmpty()) {
            return 0;
        }
        
        // 统一交由租户拦截器处理，不在此处前置要求 tenantId
        
        try {
            LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.in(User::getId, userIds)
                        .eq(User::getDeleted, 0)  // 只删除未删除的记录
                        .set(User::getDeleted, 1)
                        .set(User::getUpdateTime, LocalDateTime.now());
            
            int count = userMapper.update(null, updateWrapper);
            
            if (count > 0) {
                String currentTenantId = com.kexio.auth.context.TenantContextHolder.getTenant();
                logger.info("批量删除用户成功: userIds={}, count={}", userIds, count);
                
                // 异步清理相关数据
                cleanupBatchUserData(userIds, currentTenantId);
            }
            
            return count;
            
        } catch (Exception e) {
            logger.error("批量删除用户失败: userIds={}, error={}", userIds, e.getMessage(), e);
            throw BusinessException.operationFailed("批量删除用户失败: " + e.getMessage());
        }
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int batchUpdateUserStatus(List<String> userIds, Integer status) {
        if (userIds == null || userIds.isEmpty()) {
            return 0;
        }
        
        if (status == null) {
            throw new IllegalArgumentException("状态值不能为空");
        }
        
        if (status != 0 && status != 1) {
            throw new IllegalArgumentException("状态值必须为0（禁用）或1（启用）");
        }
        
        try {
            LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.in(User::getId, userIds)
                        .eq(User::getDeleted, 0)  // 只更新未删除的记录
                        .set(User::getStatus, status)
                        .set(User::getUpdateTime, LocalDateTime.now());
            
            int count = userMapper.update(null, updateWrapper);
            
            logger.info("批量更新用户状态成功: userIds={}, status={}, count={}", 
                userIds, status, count);
            
            return count;
            
        } catch (Exception e) {
            logger.error("批量更新用户状态失败: userIds={}, status={}, error={}", 
                userIds, status, e.getMessage(), e);
            throw BusinessException.operationFailed("批量更新用户状态失败: " + e.getMessage());
        }
    }
    
    @Override
    public boolean banUser(String userId, String reason, int banDays) {
        if (userId == null) {
            throw new IllegalArgumentException("用户ID不能为空");
        }
        
        try {
            User user = getUserById(userId);
            if (user == null) {
                throw new IllegalArgumentException("用户不存在");
            }
            
            cn.dev33.satoken.stp.StpUtil.kickout(userId);
            
            long banSeconds = banDays == -1 ? -1 : banDays * 86400L;
            cn.dev33.satoken.stp.StpUtil.disable(userId, banSeconds);
            
            user.setStatus(0);
            user.setUpdateTime(LocalDateTime.now());
            userMapper.updateById(user);
            
            logger.info("封禁用户成功: userId={}, reason={}, banDays={}", userId, reason, banDays);
            
            try {
                notifyUserPermissionChanged(userId, user.getTenantId(), "用户被封禁: " + reason);
            } catch (Exception e) {
                logger.debug("权限变更通知失败: {}", e.getMessage());
            }
            
            return true;
            
        } catch (Exception e) {
            logger.error("封禁用户失败: userId={}, error={}", userId, e.getMessage(), e);
            throw BusinessException.operationFailed("封禁用户失败: " + e.getMessage());
        }
    }
    
    @Override
    public boolean unbanUser(String userId) {
        if (userId == null) {
            throw new IllegalArgumentException("用户ID不能为空");
        }
        
        try {
            User user = getUserById(userId);
            if (user == null) {
                throw new IllegalArgumentException("用户不存在");
            }
            
            cn.dev33.satoken.stp.StpUtil.untieDisable(userId);
            
            user.setStatus(1);
            user.setUpdateTime(LocalDateTime.now());
            userMapper.updateById(user);
            
            logger.info("解封用户成功: userId={}", userId);
            
            // 触发权限变更通知
            try {
                notifyUserPermissionChanged(userId, user.getTenantId(), "用户解封");
            } catch (Exception e) {
                logger.debug("权限变更通知失败: {}", e.getMessage());
            }
            
            return true;
            
        } catch (Exception e) {
            logger.error("解封用户失败: userId={}, error={}", userId, e.getMessage(), e);
            throw BusinessException.operationFailed("解封用户失败: " + e.getMessage());
        }
    }
    
    /**
     * 异步清理批量删除用户的相关数据
     */
    private void cleanupBatchUserData(List<String> userIds, String tenantId) {
        try {
            // 清理用户角色关联
            LambdaQueryWrapper<com.kexio.user.entity.UserRole> roleWrapper = new LambdaQueryWrapper<>();
            roleWrapper.in(com.kexio.user.entity.UserRole::getUserId, userIds)
;
            userRoleMapper.delete(roleWrapper);
            
            // 清理用户数据权限关联  
            LambdaQueryWrapper<com.kexio.user.entity.UserDataScope> dataScopeWrapper = new LambdaQueryWrapper<>();
            dataScopeWrapper.in(com.kexio.user.entity.UserDataScope::getUserId, userIds)
;
            userDataScopeMapper.delete(dataScopeWrapper);
            
            logger.debug("批量清理用户相关数据完成: userIds={}, tenantId={}", userIds, tenantId);
            
        } catch (Exception e) {
            logger.warn("批量清理用户相关数据失败: userIds={}, tenantId={}, error={}", userIds, tenantId, e.getMessage());
        }
    }

    @Override
    public com.kexio.user.dto.UserStatistics getUserStatistics() {
        try {
            com.kexio.user.dto.UserStatistics statistics = new com.kexio.user.dto.UserStatistics();
            
            // 统计用户总数
            Long totalUsers = countUsers();
            statistics.setTotalUsers(totalUsers != null ? totalUsers : 0L);
            
            // 统计活跃用户数
            Long activeUsers = countActiveUsers();
            statistics.setActiveUsers(activeUsers != null ? activeUsers : 0L);
            
            // 统计禁用用户数
            LambdaQueryWrapper<User> disabledWrapper = new LambdaQueryWrapper<>();
            disabledWrapper.eq(User::getStatus, 0);
            long disabledUsers = userMapper.selectCount(disabledWrapper);
            statistics.setDisabledUsers(disabledUsers);
            
            // 统计被锁定用户数
            LambdaQueryWrapper<User> lockedWrapper = new LambdaQueryWrapper<>();
            lockedWrapper
                        .isNotNull(User::getLockTime)
                        .gt(User::getUnlockTime, LocalDateTime.now());
            long lockedUsers = userMapper.selectCount(lockedWrapper);
            statistics.setLockedUsers(lockedUsers);
            
            // UserStatistics类已经有内置的活跃率计算方法，无需单独设置
            // 活跃率可以通过 statistics.getActiveUserRate() 获取
            
            return statistics;
            
        } catch (RuntimeException e) {
            logger.error("获取用户统计信息失败: error={}", e.getMessage(), e);
            throw BusinessException.operationFailed("获取用户统计信息失败: " + e.getMessage());
        }
    }
    
    // ==================== 内部认证接口实现 ====================
    
    @Override
    public com.kexio.auth.dto.UserAuthInfo getUserAuthInfo(String username) {
        if (!StringUtils.hasText(username)) {
            return null;
        }
        
        // 查询用户信息
        User user = getUserByUsername(username);
        if (user == null || !user.isEnabled()) {
            return null;
        }
        
        return getUserAuthInfoById(user.getId());
    }
    
    @Override
    public com.kexio.auth.dto.UserAuthInfo getUserAuthInfoById(String userId) {
        if (userId == null) {
            return null;
        }
        
        // Sa-Token架构下简化认证信息获取
        logger.debug("🚀 Sa-Token架构下获取用户认证信息: userId={}", userId);
        
        // Sa-Token架构下用户认证信息获取已简化，使用直接查询
        try {
            return buildUserAuthInfoFromDB(userId);
        } catch (Exception e) {
            logger.error("获取用户认证信息失败: userId={}", userId, e);
            return null;
        }
    }
    
    /**
     * 从数据库构建用户认证信息 - 现代化缓存支持
     */
    private UserAuthInfo buildUserAuthInfoFromDB(String userId) {
        try {
            // 查询用户信息
            User user = getUserById(userId);
            if (user == null || !user.isEnabled()) {
                return null;
            }
            
            // 查询用户角色
            List<String> roles = getUserRoles(userId);
            
            // 查询用户权限
            List<String> permissions = getUserPermissions(userId);
            
            // 查询用户菜单（结果在下方按需再次获取并封装，无需此处局部变量）
            getUserMenus(userId);
            
            // 查询数据权限上下文（Sa-Token架构下简化）
            Object dataScopeContext = getUserDataScope(userId);
            
            // 构建用户认证信息
            com.kexio.auth.dto.UserAuthInfo authInfo = new com.kexio.auth.dto.UserAuthInfo();
            authInfo.setUserId(user.getId());
            authInfo.setTenantId(user.getTenantId());
            authInfo.setUsername(user.getUsername());
            authInfo.setNickname(user.getNickname());
            authInfo.setDeptId(user.getDeptId());
            authInfo.setStatus(user.getStatus());
            authInfo.setPermVersion(user.getPermVersion());
            authInfo.setLastLoginTime(user.getLastLoginTime());
            
            // 转换角色信息（从数据库查询真实角色信息）
            if (roles != null && !roles.isEmpty()) {
                List<com.kexio.auth.dto.RoleInfo> roleInfos = roles.stream()
                    .map(roleCode -> {
                        com.kexio.auth.dto.RoleInfo roleInfo = new com.kexio.auth.dto.RoleInfo();
                        roleInfo.setRoleCode(roleCode);
                        try {
                            Role dbRole = roleMapper.selectByRoleCodeAndTenant(roleCode);
                            if (dbRole != null) {
                                roleInfo.setRoleName(dbRole.getName());
                                roleInfo.setDescription(dbRole.getDescription());
                                roleInfo.setStatus(dbRole.getStatus());
                            } else {
                                roleInfo.setRoleName(roleCode);
                        roleInfo.setStatus(StatusEnum.ENABLED.getValue());
                            }
                        } catch (Exception e) {
                            // 查询失败时降级为最小信息
                            roleInfo.setRoleName(roleCode);
                            roleInfo.setStatus(StatusEnum.ENABLED.getValue());
                        }
                        return roleInfo;
                    })
                    .collect(Collectors.toList());
                authInfo.setRoles(roleInfos);
            }
            
            // 设置权限集合
            authInfo.setPermissions(permissions != null ? Set.copyOf(permissions) : Set.of());
            
            // Sa-Token架构下简化数据权限信息设置
            if (dataScopeContext != null) {
                com.kexio.auth.dto.DataScopeInfo dataScopeInfo = new com.kexio.auth.dto.DataScopeInfo();
                dataScopeInfo.setUserId(userId);
                dataScopeInfo.setScopeType("SELF"); // Sa-Token架构下默认自己权限
                dataScopeInfo.setUserDeptId(user.getDeptId());
                // 简化数据权限逻辑
                String dataScopeType = "SELF";
                if (dataScopeType != null) {
                    switch (dataScopeType) {
                        case "CUSTOM" -> setDataScopeCustomDepts(dataScopeInfo, getUserCustomDeptIds(user.getId()));
                        case "DEPT" -> {
                            String onlyDept = getUserDeptId(user);
                            if (onlyDept != null) setDataScopeDepts(dataScopeInfo, Collections.singletonList(onlyDept));
                        }
                        case "DEPT_AND_CHILD" -> setDataScopeDepts(dataScopeInfo, getDeptAndChildIds(user.getDeptId()));
                        case "ALL" -> {}
                        default -> {}
                    }
                }
                logger.debug("设置数据权限信息: dataScopeType={}, deptIds={}", dataScopeType, dataScopeInfo.getDeptIds());
                authInfo.setDataScope(dataScopeInfo);
            }
            
            // ✅ 优化：设置树形菜单（与登录接口保持一致）
            // 根据用户角色设置菜单信息
            if (roles != null && !roles.isEmpty()) {
                try {
                    // ✅ 使用 MenuService 获取树形菜单（与 AuthServiceImpl 一致）
                    List<Menu> menuTree = menuService.getUserMenuTree(user.getId(), user.getTenantId());
                    List<com.kexio.auth.dto.MenuInfo> menuInfoTree = convertMenuTreeToMenuInfoForAuth(menuTree);
                    if (menuInfoTree != null && !menuInfoTree.isEmpty()) {
                        authInfo.setMenus(menuInfoTree);
                        logger.debug("为用户设置树形菜单: userId={}, rootCount={}, totalCount={}", 
                            user.getId(), menuInfoTree.size(), countTotalMenus(menuInfoTree));
                    }
                } catch (Exception e) {
                    logger.warn("获取用户菜单失败: userId={}, error={}", user.getId(), e.getMessage());
                    // 菜单获取失败不影响认证，设置空列表
                    authInfo.setMenus(new ArrayList<>());
                }
            }
            
            logger.debug("获取用户认证信息成功: userId={}, tenantId={}, roles={}, permissions={}", 
                        userId, user.getTenantId(), roles != null ? roles.size() : 0, permissions != null ? permissions.size() : 0);
            
            return authInfo;
            
        } catch (RuntimeException e) {
            logger.error("获取用户认证信息失败: userId={}, error={}", userId, e.getMessage(), e);
            return null;
        }
    }
    
    // ==================== 权限变更通知机制 ====================
    
    /**
     * 通知用户权限变更 - 事务提交后执行
     * 
     * 执行步骤：
     * 1. 自增用户权限版本（user_perm_version表）
     * 2. 调用权限变更通知器（清理缓存、失效会话等）
     * 3. 记录权限变更日志
     * 
     * @param tenantId 租户ID
     * @param userId 用户ID
     * @param reason 变更原因
     */
    protected void internalNotifyUserPermissionChanged(String tenantId, String userId, String reason) {
        if (userId == null) {
            logger.warn("用户权限变更通知参数无效: tenantId={}, userId={}", tenantId, userId);
            return;
        }
        
        try {
            // 使用事务同步机制确保在事务提交后执行
            if (TransactionSynchronizationManager.isSynchronizationActive()) {
                TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                    @Override
                    public void afterCommit() {
                        executePermissionChangeNotification(tenantId, userId, reason);
                    }
                });
                
                logger.debug("已注册用户权限变更通知: tenantId={}, userId={}, reason={}", tenantId, userId, reason);
            } else {
                // 如果没有事务上下文，直接执行
                logger.warn("无事务上下文，立即执行权限变更通知: tenantId={}, userId={}", tenantId, userId);
                executePermissionChangeNotification(tenantId, userId, reason);
            }
            
        } catch (RuntimeException e) {
            logger.error("注册用户权限变更通知失败: tenantId={}, userId={}, reason={}, error={}", 
                tenantId, userId, reason, e.getMessage(), e);
        }
    }
    
    /**
     * 通知批量用户权限变更 - 事务提交后执行
     * 
     * @param tenantId 租户ID
     * @param userIds 用户ID列表
     * @param reason 变更原因
     */
    protected void notifyBatchUserPermissionChanged(String tenantId, List<String> userIds, String reason) {
        if (CollectionUtils.isEmpty(userIds)) {
            logger.warn("批量用户权限变更通知参数无效: tenantId={}, userIdCount={}", 
                tenantId, userIds != null ? userIds.size() : 0);
            return;
        }
        
        try {
            // 使用事务同步机制确保在事务提交后执行
            if (TransactionSynchronizationManager.isSynchronizationActive()) {
                TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                    @Override
                    public void afterCommit() {
                        for (String userId : userIds) {
                            if (userId != null) {
                                executePermissionChangeNotification(tenantId, userId, reason);
                            }
                        }
                    }
                });
                
                logger.debug("已注册批量用户权限变更通知: tenantId={}, userCount={}, reason={}", 
                    tenantId, userIds.size(), reason);
            } else {
                // 如果没有事务上下文，直接执行
                logger.warn("无事务上下文，立即执行批量权限变更通知: tenantId={}, userCount={}", 
                    tenantId, userIds.size());
                for (String userId : userIds) {
                    if (userId != null) {
                        executePermissionChangeNotification(tenantId, userId, reason);
                    }
                }
            }
            
        } catch (RuntimeException e) {
            logger.error("注册批量用户权限变更通知失败: tenantId={}, userCount={}, reason={}, error={}", 
                tenantId, userIds.size(), reason, e.getMessage(), e);
        }
    }
    
    /**
     * 执行权限变更通知 - 实际执行逻辑
     * 
     * @param tenantId 租户ID
     * @param userId 用户ID
     * @param reason 变更原因
     */
    private void executePermissionChangeNotification(String tenantId, String userId, String reason) {
        try {
            logger.info("执行用户权限变更通知: tenantId={}, userId={}, reason={}", tenantId, userId, reason);
            
            // 1. 自增用户权限版本
            incrementUserPermissionVersion(tenantId, userId, reason);
            
            // 1.1 Sa-Token架构下无需同步Redis投影（权限存储在Session中）
            logger.debug("Sa-Token架构下跳过权限投影同步: userId={}, tenantId={}", userId, tenantId);
            
            // 2. Sa-Token架构下简化权限变更处理：清理用户相关缓存
            logger.debug("Sa-Token架构下权限变更处理简化: tenantId={}, userId={}", tenantId, userId);
            
            // 3. 记录权限变更审计日志
            logPermissionChange(tenantId, userId, reason);
            
        } catch (Exception e) {
            logger.error("执行权限变更通知失败: tenantId={}, userId={}, reason={}, error={}", 
                tenantId, userId, reason, e.getMessage(), e);
        }
    }
    
    /**
     * 自增用户权限版本
     * 
     * @param tenantId 租户ID
     * @param userId 用户ID
     * @param reason 变更原因
     */
    private void incrementUserPermissionVersion(String tenantId, String userId, String reason) {
        // Sa-Token架构下无需权限版本管理，权限信息存储在Session中
        logger.debug("Sa-Token架构下跳过权限版本管理: tenantId={}, userId={}, reason={}", tenantId, userId, reason);
    }
    
    /**
     * 记录权限变更审计日志
     * 
     * @param tenantId 租户ID
     * @param userId 用户ID
     * @param reason 变更原因
     */
    private void logPermissionChange(String tenantId, String userId, String reason) {
        try {
            // 记录权限变更审计日志
            recordPermissionChangeAudit(tenantId, userId, "PERMISSION_CHANGE", 
                "权限变更: " + reason, null);
            // 可以记录到专门的审计日志表或者审计日志文件
            logger.info("权限变更审计日志: tenantId={}, userId={}, reason={}, timestamp={}", 
                tenantId, userId, reason, LocalDateTime.now());
        } catch (Exception e) {
            logger.error("记录权限变更审计日志失败: tenantId={}, userId={}, reason={}, error={}", 
                tenantId, userId, reason, e.getMessage(), e);
        }
    }
    
    /**
     * 检查是否需要权限变更通知
     * 
     * @param oldUser 旧用户信息
     * @param newUser 新用户信息
     * @return 是否需要通知
     */
    protected boolean needsPermissionChangeNotification(User oldUser, User newUser) {
        if (oldUser == null || newUser == null) {
            return false;
        }
        
        // 检查影响权限的字段变更
        boolean statusChanged = !Objects.equals(oldUser.getStatus(), newUser.getStatus());
        boolean deptChanged = !Objects.equals(oldUser.getDeptId(), newUser.getDeptId());
        
        return statusChanged || deptChanged;
    }
    
    // ==================== 私有辅助方法 ====================
    
    /**
     * 生成安全的随机密码
     * 
     * @return 随机密码（12位，包含大小写字母、数字和特殊字符）
     */
    private String generateRandomPassword() {
        // 使用SecureRandom提供密码学安全的随机数
        String upperCase = "ABCDEFGHJKMNPQRSTUVWXYZ";
        String lowerCase = "abcdefghjkmnpqrstuvwxyz";
        String digits = "23456789";
        String special = "!@#$%^&*";
        String allChars = upperCase + lowerCase + digits + special;
        
        java.security.SecureRandom random = new java.security.SecureRandom();
        StringBuilder password = new StringBuilder();
        
        // 确保密码包含各种字符类型
        password.append(upperCase.charAt(random.nextInt(upperCase.length())));
        password.append(lowerCase.charAt(random.nextInt(lowerCase.length())));
        password.append(digits.charAt(random.nextInt(digits.length())));
        password.append(special.charAt(random.nextInt(special.length())));
        
        // 填充到12位
        for (int i = 4; i < 12; i++) {
            password.append(allChars.charAt(random.nextInt(allChars.length())));
        }
        
        // 打乱顺序
        char[] chars = password.toString().toCharArray();
        for (int i = chars.length - 1; i > 0; i--) {
            int j = random.nextInt(i + 1);
            char temp = chars[i];
            chars[i] = chars[j];
            chars[j] = temp;
        }
        
        return new String(chars);
    }
    
    /**
     * 将用户数据导出为CSV格式
     * 
     * @param users 用户列表
     * @param response HTTP响应
     */
    private void exportUsersAsCsv(List<User> users, jakarta.servlet.http.HttpServletResponse response) throws java.io.IOException {
        response.setContentType("text/csv; charset=UTF-8");
        
        try (java.io.PrintWriter writer = response.getWriter()) {
            // 写入BOM以支持中文
            writer.write("\uFEFF");
            
            // CSV头部
            writer.println("用户ID,用户名,昵称,手机号,邮箱,部门ID,状态,创建时间,最后登录时间");
            
            // CSV数据
            for (User user : users) {
                writer.printf("\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"\n",
                    escapecsv(user.getId()),
                    escapecsv(user.getUsername()),
                    escapecsv(user.getNickname()),
                    escapecsv(user.getPhone()),
                    escapecsv(user.getEmail()),
                    escapecsv(user.getDeptId()),
                    (user.getStatus() != null && user.getStatus() == 1) ? "启用" : (user.getStatus() != null ? "禁用" : ""),
                    user.getCreateTime() != null ? user.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) : "",
                    user.getLastLoginTime() != null ? user.getLastLoginTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) : ""
                );
            }
            
            writer.flush();
        }
    }
    
    /**
     * CSV字符串转义
     */
    private String escapecsv(Object value) {
        if (value == null) {
            return "";
        }
        return value.toString().replace("\"", "\"\"");
    }
    
    /**
     * 记录权限变更审计日志
     * 
     * @param tenantId 租户ID
     * @param userId 用户ID
     * @param action 操作类型
     * @param description 操作描述
     * @param details 详细信息
     */
    private void recordPermissionChangeAudit(String tenantId, String userId, String action, 
                                           String description, String details) {
        try {
            // 实现审计日志记录到数据库
            // 获取当前操作用户信息
            UserAuthInfo currentUser = AuthContextUtils.getCurrentUser();
            String operatorId = currentUser != null ? currentUser.getUserId() : com.kexio.user.constants.SystemConstants.SYSTEM_USER_ID;
            String operatorName = currentUser != null ? currentUser.getUsername() : com.kexio.user.constants.SystemConstants.SYSTEM_USERNAME;
            
            // 构建审计日志信息
            String auditMessage = String.format(
                "租户[%s] 用户[%s] 执行权限操作: %s - %s %s", 
                tenantId, 
                userId, 
                action, 
                description,
                details != null ? "(" + details + ")" : ""
            );
            
            logger.info("权限审计: {}", auditMessage);
            
            // 构建详细的审计信息
            Map<String, Object> auditData = new HashMap<>();
            auditData.put("tenantId", tenantId);
            auditData.put("targetUserId", userId);
            auditData.put("operatorId", operatorId);
            auditData.put("operatorName", operatorName);
            auditData.put("action", action);
            auditData.put("description", description);
            auditData.put("details", details);
            auditData.put("timestamp", LocalDateTime.now());
            auditData.put("module", "USER_PERMISSION");
            auditData.put("level", "IMPORTANT");
            
            // 实际实现中应该：
            // 1. 创建 AuditLog 实体对象
            // 2. 设置IP地址、User-Agent等上下文信息  
            // 3. 保存到 sys_audit_log 表
            // 4. 可选：发送到审计服务或消息队列进行异步处理
            
            // 当前记录到日志系统，生产环境应保存到数据库
            logger.warn("AUDIT_LOG: {}", auditData);
            
        } catch (Exception e) {
            // 审计日志记录失败不应该影响业务流程
            logger.error("记录权限变更审计日志异常: tenantId={}, userId={}, action={}", 
                        tenantId, userId, action, e);
        }
    }
    
    @Override
    public void notifyUserPermissionChanged(String userId, String tenantId, String reason) {
        if (!StringUtils.hasText(userId) || !StringUtils.hasText(tenantId)) {
            logger.warn("用户权限变更通知参数无效: userId={}, tenantId={}", userId, tenantId);
            return;
        }
        
        try {
            logger.info("通知用户权限变更: userId={}, tenantId={}, reason={}", userId, tenantId, reason);
            
            // 1. Sa-Token架构下无需权限版本管理
            logger.debug("Sa-Token架构下跳过权限版本管理: userId={}, tenantId={}", userId, tenantId);
            
            // 2. Sa-Token架构下无需写穿Redis权限画像（权限存储在Session中）
            logger.debug("Sa-Token架构下跳过权限投影写穿: userId={}, tenantId={}", userId, tenantId);
            
            // 3. 记录审计日志
            try {
                recordPermissionChangeAudit(tenantId, userId, "PERMISSION_CHANGED", 
                                          String.format("用户权限变更: %s", reason), null);
            } catch (Exception e) {
                logger.warn("记录权限变更审计日志失败: userId={}, tenantId={}, error={}", 
                           userId, tenantId, e.getMessage());
            }
            
            // 4. Sa-Token架构下简化权限变更通知
            logger.debug("Sa-Token架构下简化权限变更处理: userId={}, tenantId={}", userId, tenantId);
            
        } catch (Exception e) {
            logger.error("处理用户权限变更通知异常: userId={}, tenantId={}, reason={}", 
                        userId, tenantId, reason, e);
            // 不抛出异常，避免影响调用方的业务流程
        }
    }
    
    // ==================== 私有辅助方法 ====================
    
    /**
     * 获取用户自定义部门ID列表
     */
    private List<String> getUserCustomDeptIds(String userId) {
        // 使用 UserDataScopeMapper 查询用户自定义数据权限部门ID
        try {
            if (userDataScopeMapper != null) {
                List<String> deptIds = userDataScopeMapper.selectDeptIdsByUserIdAndTenant(userId);
                return deptIds != null ? deptIds : Collections.emptyList();
            }
        } catch (RuntimeException e) {
            logger.warn("获取用户自定义部门ID失败: userId={}, error={}", userId, e.getMessage());
        }
        return Collections.emptyList();
    }
    
    /**
     * 获取部门及其子部门ID列表
     */
    private List<String> getDeptAndChildIds(String deptId) {
        // 通过 DeptMapper 递归查询部门及其子部门
        try {
            if (!StringUtils.hasText(deptId)) {
                return Collections.emptyList();
            }
            if (deptMapper != null) {
                List<String> ids = deptMapper.selectDeptAndChildIds(deptId);
                return ids != null ? ids : Collections.emptyList();
            }
        } catch (RuntimeException e) {
            logger.warn("获取部门及子部门失败: deptId={}, error={}", deptId, e.getMessage());
        }
        return Collections.emptyList();
    }
    
    // ❌ 已删除: getUserMenusForAuth() - 返回扁平菜单列表
    // 原因: 已改为使用 menuService.getUserMenuTree() 获取树形菜单，与 AuthServiceImpl 保持一致
    // 优化效果: 统一菜单数据结构，Session 和登录响应都使用树形结构
    
    /**
     * 设置数据权限的自定义部门列表
     */
    private void setDataScopeCustomDepts(com.kexio.auth.dto.DataScopeInfo dataScopeInfo, List<String> customDeptIds) {
        try {
            logger.debug("设置自定义部门权限: deptIds={}", customDeptIds);
            if (dataScopeInfo != null && customDeptIds != null) {
                dataScopeInfo.setCustomDeptIds(customDeptIds);
            }
            
        } catch (RuntimeException e) {
            logger.warn("设置自定义部门权限失败: deptIds={}, error={}", customDeptIds, e.getMessage());
        }
    }
    
    /**
     * 设置数据权限的部门列表
     */
    private void setDataScopeDepts(com.kexio.auth.dto.DataScopeInfo dataScopeInfo, List<String> deptIds) {
        try {
            logger.debug("设置部门权限: deptIds={}", deptIds);
            if (dataScopeInfo != null && deptIds != null) {
                java.util.List<String> ids = deptIds.stream().map(String::valueOf).collect(Collectors.toList());
                dataScopeInfo.setDeptIds(ids);
            }
            
        } catch (RuntimeException e) {
            logger.warn("设置部门权限失败: deptIds={}, error={}", deptIds, e.getMessage());
        }
    }
    
    /**
     * 获取用户部门ID
     */
    private String getUserDeptId(User user) {
        String deptId = user.getDeptId();
        return StringUtils.hasText(deptId) ? deptId : null;
    }
    
    
    
    /**
     * 获取角色权限列表
     */
    private List<String> getRolePermissions(String roleCode, String tenantId) {
        try {
            if (!StringUtils.hasText(roleCode) || !StringUtils.hasText(tenantId)) {
                return Collections.emptyList();
            }
            // 先查角色ID
            Role role = roleMapper.selectByRoleCodeAndTenant(roleCode);
            if (role == null || role.getId() == null) {
                return Collections.emptyList();
            }
            // 查权限编码
            List<String> codes = roleMapper.selectRolePermissionCodes(role.getId());
            return codes != null ? codes : Collections.emptyList();
        } catch (RuntimeException e) {
            logger.error("获取角色权限失败: roleCode={}, tenantId={}, error={}", roleCode, tenantId, e.getMessage());
            return Collections.emptyList();
        }
    }
    
    /**
     * 获取角色数据权限范围类型
     */
    // 占位方法移除：getRoleDataScopeType
    
    /**
     * 查询用户自定义数据权限范围
     */
    
    
    /**
     * 查询部门层级结构
     */
    
    
    
    
    /**
     * 获取用户所属部门ID
     */
    // 占位方法移除：getUserDeptId(String,String)
    
    /**
     * 查询用户认证菜单
     */
    
    
    /**
     * 获取角色菜单代码列表
     */
    // 占位方法移除：getRoleMenuCodes
    
    /**
     * 根据菜单代码构建菜单信息
     */
    // 占位方法移除：buildMenuInfoFromCode
    
    /**
     * 根据菜单代码获取菜单名称
     */
    // 占位方法移除：getMenuNameByCode
    
    /**
     * 根据菜单代码获取菜单类型
     */
    // 占位方法移除：getMenuTypeByCode
    
    /**
     * 根据菜单代码获取路径
     */
    // 占位方法移除：getMenuPathByCode
    
    /**
     * 根据菜单代码获取组件
     */
    // 占位方法移除：getMenuComponentByCode
    
    /**
     * 转换Role实体为RoleInfo DTO
     */
    // 占位方法移除：convertToRoleInfo
    
    
    /**
     * 根据菜单代码获取排序号
     */
    // 占位方法移除：getMenuOrderByCode
    
    /**
     * 从数据库同步用户角色
     */
    private boolean syncUserRolesFromDatabase(String userId, List<String> roleIds, Long operatorId) {
        try {
            // 1. 删除用户现有角色关联
            userRoleMapper.delete(
                new LambdaQueryWrapper<UserRole>()
                    .eq(UserRole::getUserId, userId)
            );
            
            // 2. ✅ 批量同步新的角色关联（一条SQL搞定）
            if (roleIds != null && !roleIds.isEmpty()) {
                String createdBy = AuthContextUtils.getCurrentUserId();
                userRoleMapper.batchAssignRoles(userId, roleIds, createdBy);
            }
            
            logger.info("同步用户角色成功: userId={}, roleCount={}", 
                       userId, roleIds != null ? roleIds.size() : 0);
            
            return true;
            
        } catch (Exception e) {
            logger.error("同步用户角色失败: userId={}, error={}", userId, e.getMessage(), e);
            return false;
        }
    }
    
    /**
     * 根据菜单代码获取图标
     */
    // 占位方法移除：getMenuIconByCode
    
    /**
     * 批量删除用户角色关联
     */
    private boolean batchDeleteUserRoles(String userId, List<String> roleIds) {
        try {
            if (roleIds == null || roleIds.isEmpty()) {
                logger.warn("角色ID列表为空，跳过删除操作: userId={}", userId);
                return true;
            }
            
            // 批量删除用户指定的角色关联
            LambdaUpdateWrapper<UserRole> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.eq(UserRole::getUserId, userId)
                        .in(UserRole::getRoleId, roleIds)
                        .set(UserRole::getDeleted, true)
                        .set(UserRole::getUpdateTime, LocalDateTime.now());
            
            userRoleMapper.update(null, updateWrapper);
            
            logger.info("批量删除用户角色关联成功: userId={}, deletedRoleCount={}", 
                       userId, roleIds.size());
            
            return true;
            
        } catch (Exception e) {
            logger.error("批量删除用户角色关联失败: userId={}, roleIds={}, error={}", 
                        userId, roleIds, e.getMessage(), e);
            return false;
        }
    }
    
    // ==================== JWT权限同步相关方法 ====================
    
    @Override
    public Set<String> findUsersByPermission(String permissionCode) {
        // Sa-Token架构下简化实现，返回空集合
        // 具体实现需要在UserMapper中添加相应方法
        logger.debug("Sa-Token架构下简化权限用户查找: permissionCode={}", permissionCode);
        return Collections.emptySet();
    }
    
    @Override
    public Set<String> findUsersByRole(String roleCode) {
        // Sa-Token架构下简化实现，返回空集合
        // 具体实现需要在UserMapper中添加相应方法
        logger.debug("Sa-Token架构下简化角色用户查找: roleCode={}", roleCode);
        return Collections.emptySet();
    }
    
    /**
     * 🎯 Sa-Token架构：用户权限同步辅助方法
     * 
     * 当用户的权限状态发生变化时，需要同步到缓存和Sa-Token Session。
     * 适用场景：
     * - 用户角色分配/移除
     * - 用户状态变更（启用/禁用/删除）
     * - 用户部门调整（影响数据权限）
     * 
     * @param userId 用户ID
     * @param reason 变更原因
     */
    private void syncUserPermissionAfterRoleChange(String userId, String reason) {
        // Sa-Token架构：权限变更自动生效，无需手动同步
        String tenantId = AuthContextUtils.getCurrentTenantId();
        if (!StringUtils.hasText(tenantId)) {
            tenantId = "default";
        }
        logger.debug("Sa-Token架构下用户权限变更自动生效: userId={}, tenantId={}, reason={}", 
                    userId, tenantId, reason);
    }
    
    /**
     * 将Menu实体树转换为MenuInfo DTO树（递归）
     * ✅ 优化：与 AuthServiceImpl 保持一致，避免重复代码
     * 
     * @param menuTree Menu实体树
     * @return MenuInfo DTO树
     */
    private List<com.kexio.auth.dto.MenuInfo> convertMenuTreeToMenuInfoForAuth(List<Menu> menuTree) {
        if (menuTree == null || menuTree.isEmpty()) {
            return new ArrayList<>();
        }
        
        return menuTree.stream()
            .map(this::convertMenuToMenuInfoForAuth)
            .collect(Collectors.toList());
    }
    
    /**
     * 将单个Menu实体转换为MenuInfo DTO（递归处理children）
     * 
     * @param menu Menu实体
     * @return MenuInfo DTO
     */
    private com.kexio.auth.dto.MenuInfo convertMenuToMenuInfoForAuth(Menu menu) {
        if (menu == null) {
            return null;
        }
        
        com.kexio.auth.dto.MenuInfo menuInfo = new com.kexio.auth.dto.MenuInfo();
        menuInfo.setId(menu.getId());
        menuInfo.setParentId(menu.getParentId());
        menuInfo.setName(menu.getName());
        menuInfo.setTitle(menu.getTitle());
        menuInfo.setPath(menu.getPath());
        menuInfo.setComponent(menu.getComponent());
        menuInfo.setIcon(menu.getIcon());
        menuInfo.setOrderNo(menu.getOrderNo());
        menuInfo.setMenuType(menu.getMenuType());
        menuInfo.setVisible(menu.getVisible());
        menuInfo.setStatus(menu.getStatus());
        menuInfo.setPermission(menu.getPermission());
        
        // 设置元数据
        if (menu.getTitle() != null || menu.getIcon() != null) {
            com.kexio.auth.dto.MenuInfo.MenuMeta meta = new com.kexio.auth.dto.MenuInfo.MenuMeta();
            meta.setTitle(menu.getTitle());
            meta.setIcon(menu.getIcon());
            menuInfo.setMeta(meta);
        }
        
        // ✅ 递归转换子菜单
        if (menu.getChildren() != null && !menu.getChildren().isEmpty()) {
            List<com.kexio.auth.dto.MenuInfo> children = menu.getChildren().stream()
                .map(this::convertMenuToMenuInfoForAuth)
                .collect(Collectors.toList());
            menuInfo.setChildren(children);
        }
        
        return menuInfo;
    }
    
    /**
     * 统计菜单树的总菜单数（递归）
     * 
     * @param menuInfoTree MenuInfo树
     * @return 总菜单数
     */
    private int countTotalMenus(List<com.kexio.auth.dto.MenuInfo> menuInfoTree) {
        if (menuInfoTree == null || menuInfoTree.isEmpty()) {
            return 0;
        }
        
        int count = menuInfoTree.size();
        for (com.kexio.auth.dto.MenuInfo menuInfo : menuInfoTree) {
            if (menuInfo.getChildren() != null && !menuInfo.getChildren().isEmpty()) {
                count += countTotalMenus(menuInfo.getChildren());
            }
        }
        return count;
    }
    
}
